// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/renderer/pepper/pepper_device_enumeration_host_helper.h"

#include "base/bind.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/macros.h"
#include "base/memory/weak_ptr.h"
#include "base/single_thread_task_runner.h"
#include "base/threading/thread_task_runner_handle.h"
#include "ipc/ipc_message.h"
#include "ppapi/c/pp_errors.h"
#include "ppapi/host/dispatch_host_message.h"
#include "ppapi/host/host_message_context.h"
#include "ppapi/host/ppapi_host.h"
#include "ppapi/host/resource_host.h"
#include "ppapi/proxy/ppapi_messages.h"
#include "ppapi/shared_impl/ppb_device_ref_shared.h"

using ppapi::host::HostMessageContext;

namespace content {

// Makes sure that StopEnumerateDevices() is called for each EnumerateDevices().
class PepperDeviceEnumerationHostHelper::ScopedEnumerationRequest
    : public base::SupportsWeakPtr<ScopedEnumerationRequest> {
public:
    // |owner| must outlive this object.
    ScopedEnumerationRequest(PepperDeviceEnumerationHostHelper* owner,
        const Delegate::DevicesCallback& callback)
        : callback_(callback)
        , requested_(false)
        , sync_call_(false)
    {
        if (!owner->document_url_.is_valid())
            return;

        requested_ = true;

        // Note that the callback passed into
        // PepperDeviceEnumerationHostHelper::Delegate::EnumerateDevices() may be
        // called synchronously. In that case, |callback| may destroy this
        // object. So we don't pass in |callback| directly. Instead, we use
        // EnumerateDevicesCallbackBody() to ensure that we always call |callback|
        // asynchronously.
        sync_call_ = true;
        DCHECK(owner->delegate_);
        owner->delegate_->EnumerateDevices(
            owner->device_type_, owner->document_url_,
            base::Bind(&ScopedEnumerationRequest::EnumerateDevicesCallbackBody,
                AsWeakPtr()));
        sync_call_ = false;
    }

    bool requested() const { return requested_; }

private:
    void EnumerateDevicesCallbackBody(
        const std::vector<ppapi::DeviceRefData>& devices)
    {
        if (sync_call_) {
            base::ThreadTaskRunnerHandle::Get()->PostTask(
                FROM_HERE,
                base::Bind(&ScopedEnumerationRequest::EnumerateDevicesCallbackBody,
                    AsWeakPtr(), devices));
        } else {
            callback_.Run(devices);
            // This object may have been destroyed at this point.
        }
    }

    PepperDeviceEnumerationHostHelper::Delegate::DevicesCallback callback_;
    bool requested_;
    bool sync_call_;

    DISALLOW_COPY_AND_ASSIGN(ScopedEnumerationRequest);
};

// Makes sure that StopMonitoringDevices() is called for each
// StartMonitoringDevices().
class PepperDeviceEnumerationHostHelper::ScopedMonitoringRequest
    : public base::SupportsWeakPtr<ScopedMonitoringRequest> {
public:
    // |owner| must outlive this object.
    ScopedMonitoringRequest(PepperDeviceEnumerationHostHelper* owner,
        const Delegate::DevicesCallback& callback)
        : owner_(owner)
        , callback_(callback)
        , requested_(false)
        , subscription_id_(0)
    {
        DCHECK(owner_);
        if (!owner_->document_url_.is_valid())
            return;

        requested_ = true;

        DCHECK(owner_->delegate_);
        // |callback| is never called synchronously by StartMonitoringDevices(),
        // so it is OK to pass it directly, even if |callback| destroys |this|.
        subscription_id_ = owner_->delegate_->StartMonitoringDevices(
            owner_->device_type_, owner_->document_url_, callback);
    }

    ~ScopedMonitoringRequest()
    {
        if (requested_ && owner_->delegate_) {
            owner_->delegate_->StopMonitoringDevices(owner_->device_type_,
                subscription_id_);
        }
    }

    bool requested() const { return requested_; }

private:
    PepperDeviceEnumerationHostHelper* const owner_;
    PepperDeviceEnumerationHostHelper::Delegate::DevicesCallback callback_;
    bool requested_;
    int subscription_id_;

    DISALLOW_COPY_AND_ASSIGN(ScopedMonitoringRequest);
};

PepperDeviceEnumerationHostHelper::PepperDeviceEnumerationHostHelper(
    ppapi::host::ResourceHost* resource_host,
    base::WeakPtr<Delegate> delegate,
    PP_DeviceType_Dev device_type,
    const GURL& document_url)
    : resource_host_(resource_host)
    , delegate_(delegate)
    , device_type_(device_type)
    , document_url_(document_url)
{
}

PepperDeviceEnumerationHostHelper::~PepperDeviceEnumerationHostHelper() { }

bool PepperDeviceEnumerationHostHelper::HandleResourceMessage(
    const IPC::Message& msg,
    HostMessageContext* context,
    int32_t* result)
{
    bool return_value = false;
    *result = InternalHandleResourceMessage(msg, context, &return_value);
    return return_value;
}

int32_t PepperDeviceEnumerationHostHelper::InternalHandleResourceMessage(
    const IPC::Message& msg,
    HostMessageContext* context,
    bool* handled)
{
    *handled = true;
    PPAPI_BEGIN_MESSAGE_MAP(PepperDeviceEnumerationHostHelper, msg)
    PPAPI_DISPATCH_HOST_RESOURCE_CALL_0(
        PpapiHostMsg_DeviceEnumeration_EnumerateDevices, OnEnumerateDevices)
    PPAPI_DISPATCH_HOST_RESOURCE_CALL(
        PpapiHostMsg_DeviceEnumeration_MonitorDeviceChange,
        OnMonitorDeviceChange)
    PPAPI_DISPATCH_HOST_RESOURCE_CALL_0(
        PpapiHostMsg_DeviceEnumeration_StopMonitoringDeviceChange,
        OnStopMonitoringDeviceChange)
    PPAPI_END_MESSAGE_MAP()

    *handled = false;
    return PP_ERROR_FAILED;
}

int32_t PepperDeviceEnumerationHostHelper::OnEnumerateDevices(
    HostMessageContext* context)
{
    if (enumerate_devices_context_.is_valid())
        return PP_ERROR_INPROGRESS;

    enumerate_.reset(new ScopedEnumerationRequest(
        this,
        base::Bind(&PepperDeviceEnumerationHostHelper::OnEnumerateDevicesComplete,
            base::Unretained(this))));
    if (!enumerate_->requested())
        return PP_ERROR_FAILED;

    enumerate_devices_context_ = context->MakeReplyMessageContext();
    return PP_OK_COMPLETIONPENDING;
}

int32_t PepperDeviceEnumerationHostHelper::OnMonitorDeviceChange(
    HostMessageContext* /* context */,
    uint32_t callback_id)
{
    monitor_.reset(new ScopedMonitoringRequest(
        this, base::Bind(&PepperDeviceEnumerationHostHelper::OnNotifyDeviceChange, base::Unretained(this), callback_id)));

    return monitor_->requested() ? PP_OK : PP_ERROR_FAILED;
}

int32_t PepperDeviceEnumerationHostHelper::OnStopMonitoringDeviceChange(
    HostMessageContext* /* context */)
{
    monitor_.reset(NULL);
    return PP_OK;
}

void PepperDeviceEnumerationHostHelper::OnEnumerateDevicesComplete(
    const std::vector<ppapi::DeviceRefData>& devices)
{
    DCHECK(enumerate_devices_context_.is_valid());

    enumerate_.reset(NULL);

    enumerate_devices_context_.params.set_result(PP_OK);
    resource_host_->host()->SendReply(
        enumerate_devices_context_,
        PpapiPluginMsg_DeviceEnumeration_EnumerateDevicesReply(devices));
    enumerate_devices_context_ = ppapi::host::ReplyMessageContext();
}

void PepperDeviceEnumerationHostHelper::OnNotifyDeviceChange(
    uint32_t callback_id,
    const std::vector<ppapi::DeviceRefData>& devices)
{
    resource_host_->host()->SendUnsolicitedReply(
        resource_host_->pp_resource(),
        PpapiPluginMsg_DeviceEnumeration_NotifyDeviceChange(callback_id,
            devices));
}

} // namespace content
